/* SD card and FAT filesystem example.
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

// This example uses SDMMC peripheral to communicate with SD card.

#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"

// 定义最大字符大小
#define EXAMPLE_MAX_CHAR_SIZE    64

// 定义日志标签
static const char *TAG = "example";

// 定义挂载点名称
#define MOUNT_POINT "/sdcard"   //挂载点名称

/**
 * @brief 向指定路径的文件写入数据
 *
 * 此函数尝试打开指定路径的文件以写入数据。如果文件打开失败，将记录错误日志并返回ESP_FAIL。
 * 如果文件打开成功，将写入数据并关闭文件，最后返回ESP_OK。
 *
 * @param path 要写入的文件的路径
 * @param data 要写入文件的数据
 * @return esp_err_t 如果操作成功返回ESP_OK，否则返回ESP_FAIL
 */
static esp_err_t s_example_write_file(const char *path, char *data)
{
    // 记录日志，提示正在打开文件
    ESP_LOGI(TAG, "Opening file %s", path);
    // 以写入模式打开文件
    FILE *f = fopen(path, "w");
    // 检查文件是否成功打开
    if (f == NULL) {
        // 记录错误日志，提示文件打开失败
        ESP_LOGE(TAG, "Failed to open file for writing");
        return ESP_FAIL;
    }
    // 向文件写入数据
    fprintf(f, data);
    // 关闭文件
    fclose(f);
    // 记录日志，提示文件写入成功
    ESP_LOGI(TAG, "File written");

    return ESP_OK;
}

/**
 * @brief 从指定路径的文件读取数据
 *
 * 此函数尝试打开指定路径的文件以读取数据。如果文件打开失败，将记录错误日志并返回ESP_FAIL。
 * 如果文件打开成功，将读取一行数据，去除换行符后记录日志并返回ESP_OK。
 *
 * @param path 要读取的文件的路径
 * @return esp_err_t 如果操作成功返回ESP_OK，否则返回ESP_FAIL
 */
static esp_err_t s_example_read_file(const char *path)
{
    // 记录日志，提示正在读取文件
    ESP_LOGI(TAG, "Reading file %s", path);
    // 以读取模式打开文件
    FILE *f = fopen(path, "r");
    // 检查文件是否成功打开
    if (f == NULL) {
        // 记录错误日志，提示文件打开失败
        ESP_LOGE(TAG, "Failed to open file for reading");
        return ESP_FAIL;
    }
    // 定义缓冲区用于存储读取的数据
    char line[EXAMPLE_MAX_CHAR_SIZE];
    // 从文件中读取一行数据
    fgets(line, sizeof(line), f);
    // 关闭文件
    fclose(f);

    // 去除换行符
    char *pos = strchr(line, '\n');
    if (pos) {
        *pos = '\0';
    }
    // 记录日志，输出读取到的数据
    ESP_LOGI(TAG, "Read from file: '%s'", line);

    return ESP_OK;
}

/**
 * @brief 应用程序主函数
 *
 * 此函数是程序的入口点，负责初始化SD卡、挂载文件系统、进行文件读写操作，最后卸载文件系统。
 */
void app_main(void)
{
    esp_err_t ret;
    // 定义挂载配置结构体
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = true,     //挂载失败是否执行格式化
        .max_files = 5,                     //最大可打开文件数
        .allocation_unit_size = 16 * 1024   //执行格式化时的分配单元大小（分配单元越大，读写越快）
    };
    // 定义SD卡结构体指针
    sdmmc_card_t *card;
    // 定义挂载点数组
    const char mount_point[] = MOUNT_POINT;
    // 记录日志，提示正在初始化SD卡
    ESP_LOGI(TAG, "Initializing SD card");

    // 记录日志，提示使用SDMMC外设
    ESP_LOGI(TAG, "Using SDMMC peripheral");

    // 默认配置，速度20MHz，使用卡槽1
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();

    // 默认的IO管脚配置
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();

    // 4位数据
    slot_config.width = 4;

    // 不适用通过IO矩阵进行映射的管脚，只使用默认支持的SDMMC管脚，可以获得最大性能
    #if 0
    slot_config.clk = CONFIG_EXAMPLE_PIN_CLK;
    slot_config.cmd = CONFIG_EXAMPLE_PIN_CMD;
    slot_config.d0 = CONFIG_EXAMPLE_PIN_D0;
    slot_config.d1 = CONFIG_EXAMPLE_PIN_D1;
    slot_config.d2 = CONFIG_EXAMPLE_PIN_D2;
    slot_config.d3 = CONFIG_EXAMPLE_PIN_D3;
    #endif
    // Enable internal pullups on enabled pins. The internal pullups
    // are insufficient however, please make sure 10k external pullups are
    // connected on the bus. This is for debug / example purpose only.
    // 管脚启用内部上拉
    slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;

    // 记录日志，提示正在挂载文件系统
    ESP_LOGI(TAG, "Mounting filesystem");
    // 挂载文件系统
    ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);

    // 检查挂载是否成功
    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            // 记录错误日志，提示挂载文件系统失败
            ESP_LOGE(TAG, "Failed to mount filesystem. ");
        } else {
            // 记录错误日志，提示初始化SD卡失败
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                     "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        return;
    }
    // 记录日志，提示文件系统挂载成功
    ESP_LOGI(TAG, "Filesystem mounted");

    // 打印SD卡信息
    sdmmc_card_print_info(stdout, card);

    // 使用POSIX和C标准库函数操作文件

    // 首先创建一个文件
    const char *file_hello = MOUNT_POINT"/hello.txt";
    // 定义数据缓冲区
    char data[EXAMPLE_MAX_CHAR_SIZE];
    // 格式化数据
    snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Hello", card->cid.name);
    // 写入文件
    ret = s_example_write_file(file_hello, data);
    if (ret != ESP_OK) {
        return;
    }

    // 定义另一个文件路径
    const char *file_foo = MOUNT_POINT"/foo.txt";
    // 检查目标文件是否存在
    struct stat st;
    if (stat(file_foo, &st) == 0) {
        // 如果存在则删除
        unlink(file_foo);
    }

    // 重命名文件
    ESP_LOGI(TAG, "Renaming file %s to %s", file_hello, file_foo);
    if (rename(file_hello, file_foo) != 0) {
        // 记录错误日志，提示重命名失败
        ESP_LOGE(TAG, "Rename failed");
        return;
    }

    // 读取文件
    ret = s_example_read_file(file_foo);
    if (ret != ESP_OK) {
        return;
    }

    // 格式化FATFS
    #if 0
    ret = esp_vfs_fat_sdcard_format(mount_point, card);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to format FATFS (%s)", esp_err_to_name(ret));
        return;
    }

   
    if (stat(file_foo, &st) == 0) {
        ESP_LOGI(TAG, "file still exists");
        return;
    } else {
        ESP_LOGI(TAG, "file doesnt exist, format done");
    }
    #endif
    const char *file_nihao = MOUNT_POINT"/nihao.txt";
    memset(data, 0, EXAMPLE_MAX_CHAR_SIZE);
    snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Nihao", card->cid.name);
    ret = s_example_write_file(file_nihao, data);
    if (ret != ESP_OK) {
        return;
    }

    //Open file for reading
    ret = s_example_read_file(file_nihao);
    if (ret != ESP_OK) {
        return;
    }

    // All done, unmount partition and disable SDMMC peripheral
    esp_vfs_fat_sdcard_unmount(mount_point, card);
    ESP_LOGI(TAG, "Card unmounted");
}
